// Copyright (c) 2020 Presto Labs Pte. Ltd.
// Author: hyuan

#include "coin2/exchange/deribit_base/api_util/rate_tracker.h"

#include <algorithm>
#include <sstream>
#include <string>

#include <boost/algorithm/string/replace.hpp>
#include <glog/logging.h>

#include "coin2/base/proto_util.h"
#include "coin2/exchange/base/order/rate_config.h"

namespace coin2::exchange::deribit::api_util {

constexpr auto EXAMPLE_FUTURES_DERIBIT = R"(
"connection_config": {
  "key_filepath": "/home/hyuan/config/deribit_trade_key.json",
  "rate_limit_info": {
    "futures_deribit": {
      "ord_sec": {
        "secs": 1,
        "reqs": 20
      },
      "req_sec": {
        "secs": 1,
        "reqs": 50
      }
    }
  }
},)";

constexpr auto EXAMPLE_OPTIONS_DERIBIT = R"(
"connection_config": {
  "key_filepath": "/home/hyuan/config/deribit_trade_key.json",
  "rate_limit_info": {
    "options_deribit": {
      "ord_sec": {
        "secs": 1,
        "reqs": 20
      },
      "req_sec": {
        "secs": 1,
        "reqs": 50
      }
    }
  }
},)";

template <class RateTracker>
void CheckFail(RateTracker&& rate_tracker, bool check_result) {
  if (!check_result) {
    LOG(INFO) << "Hit rate limit on [" << rate_tracker->GetName() << "]";
  }
}

DeribitRateTracker::DeribitRateTracker(
    const MarketExchangeApi& mea,
    const coin2::exchange::order::OrderConnectionConfig& cfg) {
  using RateConfig = coin2::exchange::base::order::RateConfig;
  if (mea.market == coin::proto::MarketType::Futures) {
    coin2::exchange::order::RateLimitInfoFuturesDeribit rate_config;
    coin2::exchange::order::OrderConnectionConfig conn_cfg;
    conn_cfg.MergeFrom(cfg);
    RateConfig::Get()->GetConfig(mea, conn_cfg, &rate_config);
    InitRateTracker(&rate_config, mea.String(), EXAMPLE_FUTURES_DERIBIT);
  } else if (mea.market == coin::proto::MarketType::Options) {
    coin2::exchange::order::RateLimitInfoOptionsDeribit rate_config;
    coin2::exchange::order::OrderConnectionConfig conn_cfg;
    conn_cfg.MergeFrom(cfg);
    RateConfig::Get()->GetConfig(mea, conn_cfg, &rate_config);
    InitRateTracker(&rate_config, mea.String(), EXAMPLE_OPTIONS_DERIBIT);
  }
}

template <class RateConfig>
void DeribitRateTracker::InitRateTracker(
    const RateConfig& rate_config, 
    std::string_view mea_str,
    std::string_view example) {
  if (!rate_config || !rate_config->has_ord_sec() || !rate_config->has_req_sec()) {
    coin2::exchange::base::order::RateConfig::CheckFail(mea_str, example);
  }

  const auto& ord_sec = rate_config->ord_sec();
  const auto& req_sec = rate_config->req_sec();
  LOG(INFO) << "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"
            << "\nOG request rate was set as below:\n"
            << "\nord_sec: [" << ord_sec.reqs() << " (reqs) / " << ord_sec.secs() << " (seconds)]"
            << "\nreq_sec: [" << req_sec.reqs() << " (reqs) / " << req_sec.secs() << " (seconds)]"
            << "\n\nOr you can configure customized rate in driver config. For example: " << example
            << "\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~";

  order_sec_rate_tracker_ =
      std::make_unique<FixedWindowRateTracker>(ord_sec.secs() * 1'000'000'000LL);
  order_sec_rate_tracker_->SetRateLimitInfo(ord_sec.reqs(), ord_sec.reqs());
  order_sec_rate_tracker_->SetName("deribit_order_rate_tracker(1 sec)");

  req_sec_rate_tracker_ =
      std::make_unique<FixedWindowRateTracker>(req_sec.secs() * 1'000'000'000LL);
  req_sec_rate_tracker_->SetRateLimitInfo(req_sec.reqs(), req_sec.reqs());
  req_sec_rate_tracker_->SetName("deribit_req_rate_tracker(1 sec)");

  // rate limit report
  rate_limit_report_writer_ = std::make_unique<RateLimitReportWriter>();
  rate_limit_report_writer_->SetRateLimitInfoString(rate_config);
}

bool DeribitRateTracker::HasEnoughRateLimit(int64_t timestamp, ActionType action_type) {
  bool ret = true;
  if (action_type == ActionType::PLACE || action_type == ActionType::CANCEL) {
    if (ret && order_sec_rate_tracker_) {
      ret &= (order_sec_rate_tracker_->GetRemaining(timestamp) > 2);
      CheckFail(order_sec_rate_tracker_, ret);
    }
  } else {
    if (ret && req_sec_rate_tracker_) {
      ret &= (req_sec_rate_tracker_->GetRemaining(timestamp) > 2);
      CheckFail(req_sec_rate_tracker_, ret);
    }
  }
  if (!ret) {
    rate_limit_report_writer_->SetHitRateLimit(timestamp, action_type);
  }
  return ret;
}

void DeribitRateTracker::Push(int32_t num_reqs, int64_t timestamp, ActionType action_type) {
  if (action_type == ActionType::PLACE || action_type == ActionType::CANCEL) {
    if (order_sec_rate_tracker_) {
      order_sec_rate_tracker_->Push(num_reqs, timestamp);
    }
  } else {
    if (req_sec_rate_tracker_) {
      req_sec_rate_tracker_->Push(num_reqs, timestamp);
    }
  }
}

void DeribitRateTracker::SetRateLimitInfoString(const google::protobuf::Message* message) {
  if (!rate_limit_report_writer_) {
    return;
  }
  rate_limit_report_writer_->SetRateLimitInfoString(message);
}

void DeribitRateTracker::WriteRateLimitReportMessage(OrderLoggerCB callback) {
  if (!callback) {
    return;
  }
  rate_limit_report_writer_->Write(callback);
}

double DeribitRateTracker::GetMaxRateUsedRatio(int64_t timestamp, ActionType action_type) {
  switch (action_type) {
    case ActionType::PLACE:
    case ActionType::CANCEL: {
      double order_sec_ratio = 0.;
      if (order_sec_rate_tracker_) {
        auto max_reqs = order_sec_rate_tracker_->MaxReq();
        auto remaining = order_sec_rate_tracker_->GetRemaining(timestamp);
        order_sec_ratio = 1.0 * (max_reqs - remaining) / max_reqs;
      }
      return order_sec_ratio;
    }
    case ActionType::QUERY: {
      if (!req_sec_rate_tracker_) {
        return 0.;
      }
      auto max_reqs = req_sec_rate_tracker_->MaxReq();
      auto remaining = req_sec_rate_tracker_->GetRemaining(timestamp);
      return 1.0 * (max_reqs - remaining) / max_reqs;
    }
    default: {
      LOG(ERROR) << "ops~ something is wrong, action_type=" << action_type;
      return 0.;
    }
  }
}

}  // namespace coin2::exchange::deribit::api_util
